Frigjør kraften i React-portaler for å lage tilgjengelige og visuelt tiltalende modaler og verktøytips, som forbedrer brukeropplevelsen og komponentstrukturen.
React-portaler: Slik mestrer du modaler og verktøytips for en bedre brukeropplevelse
I moderne webutvikling er det avgjørende å skape intuitive og engasjerende brukergrensesnitt. React, et populært JavaScript-bibliotek for å bygge brukergrensesnitt, tilbyr ulike verktøy og teknikker for å oppnå dette. Et slikt kraftig verktøy er React-portaler. Dette blogginnlegget dykker ned i verdenen av React-portaler, med fokus på deres anvendelse i bygging av tilgjengelige og visuelt tiltalende modaler og verktøytips.
Hva er React-portaler?
React-portaler tilbyr en måte å rendere et komponents barn inn i en DOM-node som eksisterer utenfor DOM-hierarkiet til forelderkomponenten. Enkelt sagt lar det deg bryte ut av det standard React-komponenttreet og sette inn elementer direkte i en annen del av HTML-strukturen. Dette er spesielt nyttig i situasjoner der du trenger å kontrollere stablekonteksten (stacking context) eller posisjonere elementer utenfor grensene til deres forelderbeholder.
Tradisjonelt renderes React-komponenter som barn av sine forelderkomponenter i DOM-en. Dette kan noen ganger føre til stil- og layoututfordringer, spesielt når man håndterer elementer som modaler eller verktøytips som må vises oppå annet innhold eller posisjoneres i forhold til visningsområdet (viewport). React-portaler gir en løsning ved å la disse elementene renderes direkte i en annen del av DOM-treet, og omgår dermed disse begrensningene.
Hvorfor bruke React-portaler?
Flere sentrale fordeler gjør React-portaler til et verdifullt verktøy i ditt React-utviklingsarsenal:
- Forbedret stilsetting og layout: Portaler lar deg posisjonere elementer utenfor forelderens beholder, og overvinner stilproblemer forårsaket av
overflow: hidden,z-index-begrensninger eller komplekse layout-restriksjoner. Se for deg en modal som må dekke hele skjermen, selv om forelderbeholderen haroverflow: hiddensatt. Portaler lar deg rendere modalen direkte ibody, og omgår denne begrensningen. - Forbedret tilgjengelighet: Portaler er avgjørende for tilgjengelighet, spesielt når man jobber med modaler. Ved å rendere modalinnholdet direkte i
body, kan du enkelt håndtere fokusfangst (focus trapping), og sikre at brukere som benytter skjermlesere eller tastaturnavigasjon forblir inne i modalen mens den er åpen. Dette er essensielt for å tilby en sømløs og tilgjengelig brukeropplevelse. - Renere komponentstruktur: Ved å rendere modal- eller verktøytipsinnhold utenfor hovedkomponenttreet, kan du holde komponentstrukturen din renere og mer håndterbar. Denne separasjonen av ansvarsområder kan gjøre koden din enklere å lese, forstå og vedlikeholde.
- Unngå problemer med stablekontekst: Stablekontekster i CSS kan være notorisk vanskelige å håndtere. Portaler hjelper deg med å unngå disse problemene ved å la deg rendere elementer direkte i roten av DOM-en, og sikrer at de alltid er korrekt posisjonert i forhold til andre elementer på siden.
Implementering av modaler med React-portaler
Modaler er et vanlig UI-mønster som brukes til å vise viktig informasjon eller be brukere om input. La oss utforske hvordan man lager en modal ved hjelp av React-portaler.
1. Opprette portalroten
Først må du opprette en DOM-node hvor modalen skal renderes. Dette gjøres vanligvis ved å legge til et div-element med en spesifikk ID i HTML-filen din (vanligvis i body):
<div id="modal-root"></div>
2. Opprette modalkomponenten
Deretter lager du en React-komponent som representerer modalen. Denne komponenten vil inneholde modalens innhold og logikk.
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
const [mounted, setMounted] = useState(false);
const modalRoot = useRef(document.getElementById('modal-root'));
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
if (!isOpen) return null;
const modalContent = (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onClose}>Lukk</button>
</div>
</div>
);
return mounted && modalRoot.current
? ReactDOM.createPortal(modalContent, modalRoot.current)
: null;
};
export default Modal;
Forklaring:
isOpenprop: Bestemmer om modalen er synlig.onCloseprop: En funksjon for å lukke modalen.childrenprop: Innholdet som skal vises inne i modalen.modalRootref: Refererer til DOM-noden hvor modalen skal renderes (#modal-root).useEffecthook: Sikrer at modalen kun renderes etter at komponenten er montert for å unngå problemer med at portalroten ikke er tilgjengelig umiddelbart.ReactDOM.createPortal: Dette er nøkkelen til å bruke React-portaler. Den tar to argumenter: React-elementet som skal renderes (modalContent) og DOM-noden hvor det skal renderes (modalRoot.current).- Klikk på overlegget: Lukker modalen. Vi bruker
e.stopPropagation()påmodal-content-diven for å forhindre at klikk inne i modalen lukker den.
3. Bruke modalkomponenten
Nå kan du bruke Modal-komponenten i applikasjonen din:
import React, { useState } from 'react';
import Modal from './Modal';
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<button onClick={openModal}>Åpne modal</button>
<Modal isOpen={isModalOpen} onClose={closeModal}>
<h2>Modalinnhold</h2>
<p>Dette er innholdet i modalen.</p>
</Modal>
</div>
);
};
export default App;
Dette eksempelet demonstrerer hvordan man kontrollerer synligheten til modalen ved hjelp av isOpen-prop-en og funksjonene openModal og closeModal. Innholdet innenfor <Modal>-taggene vil bli rendret inne i modalen.
4. Stilsetting av modalen
Legg til CSS-stiler for å posisjonere og style modalen. Her er et grunnleggende eksempel:
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Halvtransparent bakgrunn */
display: flex;
justify-content: center;
align-items: center;
z-index: 1000; /* Sørg for at den ligger over annet innhold */
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
Forklaring av CSS:
position: fixed: Sikrer at modalen dekker hele visningsområdet, uavhengig av scrolling.background-color: rgba(0, 0, 0, 0.5): Skaper et halvtransparent overlegg bak modalen.display: flex, justify-content: center, align-items: center: Sentrerer modalen horisontalt og vertikalt.z-index: 1000: Sikrer at modalen renderes over alt annet innhold på siden.
5. Tilgjengelighetshensyn for modaler
Tilgjengelighet er avgjørende ved implementering av modaler. Her er noen viktige hensyn:
- Fokushåndtering: Når modalen åpnes, bør fokus automatisk flyttes til et element inne i modalen (f.eks. det første input-feltet eller en lukkeknapp). Når modalen lukkes, bør fokus returnere til elementet som utløste åpningen av modalen. Dette oppnås ofte ved å bruke Reacts
useRef-hook for å lagre det tidligere fokuserte elementet. - Tastaturnavigasjon: Sørg for at brukere kan navigere i modalen med tastaturet (Tab-tasten). Fokus bør fanges inne i modalen, slik at brukere ikke ved et uhell tabber seg ut av den. Biblioteker som
react-focus-lockkan hjelpe med dette. - ARIA-attributter: Bruk ARIA-attributter for å gi semantisk informasjon om modalen til skjermlesere. For eksempel, bruk
aria-modal="true"på modalbeholderen ogaria-labelelleraria-labelledbyfor å gi en beskrivende etikett for modalen. - Lukkemekanisme: Tilby flere måter å lukke modalen på, for eksempel en lukkeknapp, klikk på overlegget eller ved å trykke på Escape-tasten.
Eksempel på fokushåndtering (ved bruk av useRef):
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
const [mounted, setMounted] = useState(false);
const modalRoot = useRef(document.getElementById('modal-root'));
const firstFocusableElement = useRef(null);
const previouslyFocusedElement = useRef(null);
useEffect(() => {
setMounted(true);
if (isOpen) {
previouslyFocusedElement.current = document.activeElement;
if (firstFocusableElement.current) {
firstFocusableElement.current.focus();
}
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
if (previouslyFocusedElement.current) {
previouslyFocusedElement.current.focus();
}
};
}
return () => setMounted(false);
}, [isOpen, onClose]);
if (!isOpen) return null;
const modalContent = (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h2>Modalinnhold</h2>
<p>Dette er innholdet i modalen.</p>
<input type="text" ref={firstFocusableElement} /> <!-- Første fokuserbare element -->
<button onClick={onClose}>Lukk</button>
</div>
</div>
);
return mounted && modalRoot.current
? ReactDOM.createPortal(modalContent, modalRoot.current)
: null;
};
export default Modal;
Forklaring av kode for fokushåndtering:
previouslyFocusedElement.current: Lagrer elementet som hadde fokus før modalen ble åpnet.firstFocusableElement.current: Refererer til det første fokuserbare elementet *inne i* modalen (i dette eksempelet, et tekst-input).- Når modalen åpnes (
isOpener true):- Det nåværende fokuserte elementet lagres.
- Fokus flyttes til
firstFocusableElement.current. - En hendelseslytter legges til for å lytte etter Escape-tasten, som lukker modalen.
- Når modalen lukkes (opprydningsfunksjon):
- Hendelseslytteren for Escape-tasten fjernes.
- Fokus returneres til elementet som tidligere var fokusert.
Implementering av verktøytips med React-portaler
Verktøytips er små, informative popups som vises når en bruker holder musepekeren over et element. React-portaler kan brukes til å lage verktøytips som er korrekt posisjonert, uavhengig av forelderelementets stil eller layout.
1. Opprette portalroten (hvis ikke allerede opprettet)
Hvis du ikke allerede har opprettet en portalrot for modaler, legg til et div-element med en spesifikk ID i HTML-filen din (vanligvis i body):
<div id="tooltip-root"></div>
2. Opprette verktøytipskomponenten
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Tooltip = ({ text, children, position = 'top' }) => {
const [isVisible, setIsVisible] = useState(false);
const [positionStyle, setPositionStyle] = useState({});
const [mounted, setMounted] = useState(false);
const tooltipRoot = useRef(document.getElementById('tooltip-root'));
const tooltipRef = useRef(null);
const triggerRef = useRef(null);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
const handleMouseEnter = () => {
setIsVisible(true);
updatePosition();
};
const handleMouseLeave = () => {
setIsVisible(false);
};
const updatePosition = () => {
if (!triggerRef.current || !tooltipRef.current) return;
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
let top = 0;
let left = 0;
switch (position) {
case 'top':
top = triggerRect.top - tooltipRect.height - 5; // 5px avstand
left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
break;
case 'bottom':
top = triggerRect.bottom + 5;
left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
break;
case 'left':
top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
left = triggerRect.left - tooltipRect.width - 5;
break;
case 'right':
top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
left = triggerRect.right + 5;
break;
default:
break;
}
setPositionStyle({
top: `${top}px`,
left: `${left}px`,
});
};
const tooltipContent = isVisible && (
<div className="tooltip" style={positionStyle} ref={tooltipRef}>
{text}
</div>
);
return (
<span
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
{mounted && tooltipRoot.current ? ReactDOM.createPortal(tooltipContent, tooltipRoot.current) : null}
</span>
);
};
export default Tooltip;
Forklaring:
textprop: Teksten som skal vises i verktøytipset.childrenprop: Elementet som utløser verktøytipset (elementet brukeren holder musepekeren over).positionprop: Posisjonen til verktøytipset i forhold til utløserelementet ('top', 'bottom', 'left', 'right'). Standard er 'top'.isVisiblestate: Kontrollerer synligheten til verktøytipset.tooltipRootref: Refererer til DOM-noden hvor verktøytipset skal renderes (#tooltip-root).tooltipRefref: Refererer til selve verktøytipselementet, brukt for å beregne dets dimensjoner.triggerRefref: Refererer til elementet som utløser verktøytipset (children).handleMouseEnteroghandleMouseLeave: Hendelseshåndterere for å holde musepekeren over utløserelementet.updatePosition: Beregner den korrekte posisjonen til verktøytipset basert påposition-prop-en og dimensjonene til utløser- og verktøytipselementene. Den brukergetBoundingClientRect()for å hente posisjon og dimensjoner til elementene i forhold til visningsområdet.ReactDOM.createPortal: Renderer verktøytipsinnholdet inn itooltipRoot.
3. Bruke verktøytipskomponenten
import React from 'react';
import Tooltip from './Tooltip';
const App = () => {
return (
<div>
<p>
Hold musepekeren over denne <Tooltip text="Dette er et verktøytips!\nMed flere linjer." position="bottom">teksten</Tooltip> for å se et verktøytips.
</p>
<button>
Hold <Tooltip text="Knapp-verktøytips" position="top">her</Tooltip> for verktøytips.
</button>
</div>
);
};
export default App;
Dette eksempelet viser hvordan du bruker Tooltip-komponenten for å legge til verktøytips på tekst og knapper. Du kan tilpasse verktøytipsets tekst og posisjon ved hjelp av text- og position-prop-ene.
4. Stilsetting av verktøytipset
Legg til CSS-stiler for å posisjonere og style verktøytipset. Her er et grunnleggende eksempel:
.tooltip {
position: absolute;
background-color: rgba(0, 0, 0, 0.8); /* Mørk bakgrunn */
color: white;
padding: 5px;
border-radius: 3px;
font-size: 12px;
z-index: 1000; /* Sørg for at den ligger over annet innhold */
white-space: pre-line; /* Respekter linjeskift i text-prop-en */
}
Forklaring av CSS:
position: absolute: Posisjonerer verktøytipset i forhold tiltooltip-root. FunksjonenupdatePositioni React-komponenten beregner de nøyaktigetop- ogleft-verdiene for å posisjonere verktøytipset nær utløserelementet.background-color: rgba(0, 0, 0, 0.8): Skaper en lett gjennomsiktig mørk bakgrunn for verktøytipset.white-space: pre-line: Dette er viktig for å bevare linjeskift som du kan inkludere itext-prop-en. Uten dette ville all verktøytipstekst havnet på en enkelt linje.
Globale hensyn og beste praksis
Når du utvikler React-applikasjoner for et globalt publikum, bør du vurdere disse beste praksisene:
- Internasjonalisering (i18n): Bruk et bibliotek som
react-i18nextellerFormatJSfor å håndtere oversettelser og lokalisering. Dette lar deg enkelt tilpasse applikasjonen din til forskjellige språk og regioner. For modaler og verktøytips, sørg for at tekstinnholdet er korrekt oversatt. - Støtte for høyre-til-venstre (RTL): For språk som leses fra høyre til venstre (f.eks. arabisk, hebraisk), sørg for at dine modaler og verktøytips vises korrekt. Du må kanskje justere posisjonering og stil på elementer for å imøtekomme RTL-layouter. CSS logiske egenskaper (f.eks.
margin-inline-starti stedet formargin-left) kan være nyttige. - Kulturell sensitivitet: Vær oppmerksom på kulturelle forskjeller når du designer modaler og verktøytips. Unngå å bruke bilder eller symboler som kan være støtende eller upassende i visse kulturer.
- Tidssoner og datoformater: Hvis dine modaler eller verktøytips viser datoer eller klokkeslett, sørg for at de er formatert i henhold til brukerens lokalitet og tidssone. Biblioteker som
moment.js(selv om det er eldre, er det fortsatt mye brukt) ellerdate-fnskan hjelpe med dette. - Tilgjengelighet for ulike funksjonsevner: Følg retningslinjene for tilgjengelighet (WCAG) for å sikre at dine modaler og verktøytips er brukbare for personer med nedsatt funksjonsevne. Dette inkluderer å tilby alternativ tekst for bilder, sikre tilstrekkelig fargekontrast og gi støtte for tastaturnavigasjon.
Konklusjon
React-portaler er et kraftig verktøy for å bygge fleksible og tilgjengelige brukergrensesnitt. Ved å forstå hvordan du bruker dem effektivt, kan du lage modaler og verktøytips som forbedrer brukeropplevelsen og forbedrer strukturen og vedlikeholdbarheten til dine React-applikasjoner. Husk å prioritere tilgjengelighet og globale hensyn når du utvikler for et mangfoldig publikum, og sørg for at applikasjonene dine er inkluderende og brukbare for alle.